/*
* Copyright (c) Microsoft Corporation.  All Rights Reserved.
*
* Public Mock10 API
*/
#pragma once
#include "Mock10\ExternalMockFunction.h"
#include "Mock10\ExternalMockMethod.h"
#include "Mock10\ExternalMockTarget.h"
#include "Mock10\MockFunction.h"
#include "Mock10\MockMethod.h"
#include "Mock10\MockVariable.h"
#include "WexDebug.h"
#include "WexTypes.h"

#pragma push_macro("SEALED")
#undef SEALED

#if _MSC_VER >= 1400
# define SEALED sealed
#else
# define SEALED
#endif

namespace Mock10
{
    // Factory methods for creating function and instance-method mocks
    class Mock SEALED
    {
    public:
        /////////////////////////////////////
        // Global and static function mocking
        template <typename TPrototype>
        static MockFunction<TPrototype> Function(TPrototype* pTargetFunction)
        {
            return MockFunction<TPrototype>(pTargetFunction);
        }

        template <typename TPrototype>
        static MockFunction<TPrototype> Function(TPrototype* pTargetFunction, const wchar_t* pszFilter)
        {
            Mock10::Private::WriteCriticalSectionLock lock(Mock10::Private::Detour::GetGlobalCriticalSection());
            MockFunction<TPrototype> mock = Function(pTargetFunction);
            mock.SetFilter(pszFilter);
            return mock;
        }

        template <typename TPrototype, typename TPrototype2>
        static MockFunction<TPrototype> Function(TPrototype* pTargetFunction, TPrototype2* pDetourFunction)
        {
            COMPILE_TIME_CHECK_V2((WEX::Common::AreSameType<TPrototype, TPrototype2>::value),
                    "The mock target and detour function specified have mismatched function signatures", 
                    The_mock_target_and_detour_function_specified_have_mismatched_function_signatures);
            return MockFunction<TPrototype>(pTargetFunction, pDetourFunction);
        }

        template <typename TPrototype, typename TPrototype2>
        static MockFunction<TPrototype> Function(TPrototype* pTargetFunction, TPrototype2* pDetourFunction, const wchar_t* pszFilter)
        {
            Mock10::Private::WriteCriticalSectionLock lock(Mock10::Private::Detour::GetGlobalCriticalSection());
            MockFunction<TPrototype> mock = Function(pTargetFunction, pDetourFunction);
            mock.SetFilter(pszFilter);
            return mock;
        }

        template <typename TPrototype, typename TFunctor>
        static MockFunction<TPrototype> Function(TPrototype* pTargetFunction, const TFunctor& detourFunctor)
        {
            return MockFunction<TPrototype>(pTargetFunction, detourFunctor);
        }

        template <typename TPrototype, typename TFunctor>
        static MockFunction<TPrototype> Function(TPrototype* pTargetFunction, const TFunctor& detourFunctor, const wchar_t* pszFilter)
        {
            Mock10::Private::WriteCriticalSectionLock lock(Mock10::Private::Detour::GetGlobalCriticalSection());
            MockFunction<TPrototype> mock = Function(pTargetFunction, detourFunctor);
            mock.SetFilter(pszFilter);
            return mock;
        }

        template <typename TPrototype>
        static MockFunction<TPrototype> Function(const wchar_t* pszTargetBinary, const wchar_t* pszTargetQuery, TPrototype* pDetourFunction, unsigned long expectedMatchCount = 1)
        {
            return MockFunction<TPrototype>(pszTargetBinary, pszTargetQuery, pDetourFunction, expectedMatchCount);
        }

        template <typename TPrototype>
        static MockFunction<TPrototype> Function(const wchar_t* pszTargetBinary, const wchar_t* pszTargetQuery, TPrototype* pDetourFunction, const wchar_t* pszFilter, unsigned long expectedMatchCount = 1)
        {
            Mock10::Private::WriteCriticalSectionLock lock(Mock10::Private::Detour::GetGlobalCriticalSection());
            MockFunction<TPrototype> mock = Function(pszTargetBinary, pszTargetQuery, pDetourFunction, expectedMatchCount);
            mock.SetFilter(pszFilter);
            return mock;
        }

        template <typename TPrototype>
        static MockFunction<TPrototype> Function(const wchar_t* pszTargetBinary, const wchar_t* pszTargetQuery, unsigned long expectedMatchCount = 1)
        {
            return MockFunction<TPrototype>(pszTargetBinary, pszTargetQuery, expectedMatchCount);
        }

        template <typename TPrototype>
        static MockFunction<TPrototype> Function(const wchar_t* pszTargetBinary, const wchar_t* pszTargetQuery, const wchar_t* pszFilter, unsigned long expectedMatchCount = 1)
        {
            Mock10::Private::WriteCriticalSectionLock lock(Mock10::Private::Detour::GetGlobalCriticalSection());
            MockFunction<TPrototype> mock = Function<TPrototype>(pszTargetBinary, pszTargetQuery, expectedMatchCount);
            mock.SetFilter(pszFilter);
            return mock;
        }

        /////////////////////////////////////
        // External Global and static function mocking
        template <typename TPrototype>
        static ExternalMockFunction<TPrototype> ExternalFunction(const ExternalMockTarget& target, TPrototype* pTargetFunction)
        {
            COMPILE_TIME_CHECK_V2((!WEX::Common::AreSameType<TPrototype, const wchar_t>::value),
                    "You must explicitly specify a function prototype (via TPrototype) when issuing function queries.", 
                    You_must_explicitly_specify_TPrototype_when_issuing_function_queries);

            return ExternalMockFunction<TPrototype>(target, pTargetFunction);
        }

        template <typename TPrototype>
        static ExternalMockFunction<TPrototype> ExternalFunction(const ExternalMockTarget& target, TPrototype* pTargetFunction, const wchar_t* pszFilter)
        {
            // Make sure to specify TPrototype in your call to Mock::ExternalFunction<TPrototype>(target, pszTargetBinary, pszQuery, ...)
            COMPILE_TIME_CHECK_V2((!WEX::Common::AreSameType<TPrototype, const wchar_t>::value),
                    "You must explicitly specify a function prototype (via TPrototype) when issuing function queries.", 
                    You_must_explicitly_specify_TPrototype_when_issuing_function_queries);

            return ExternalMockFunction<TPrototype>(target, pTargetFunction, pszFilter);
        }

        template <typename TPrototype>
        static ExternalMockFunction<TPrototype> ExternalFunction(const ExternalMockTarget& target, TPrototype* pTargetFunction, 
                                                                 typename ExternalMockFunction<TPrototype>::TDetourPrototype* pDetourFunction)
        {
            // Make sure to specify TPrototype in your call to Mock::ExternalFunction<TPrototype>(target, pszTargetBinary, pszQuery, ...)
            COMPILE_TIME_CHECK_V2((!WEX::Common::AreSameType<TPrototype, const wchar_t>::value),
                    "You must explicitly specify a function prototype (via TPrototype) when issuing function queries.", 
                    You_must_explicitly_specify_TPrototype_when_issuing_function_queries);

            return ExternalMockFunction<TPrototype>(target, pTargetFunction, pDetourFunction);
        }

        template <typename TPrototype>
        static ExternalMockFunction<TPrototype> ExternalFunction(const ExternalMockTarget& target, TPrototype* pTargetFunction, 
                                                                 typename ExternalMockFunction<TPrototype>::TDetourPrototype* pDetourFunction, const wchar_t* pszFilter)
        {
            // Make sure to specify TPrototype in your call to Mock::ExternalFunction<TPrototype>(target, pszTargetBinary, pszQuery, ...)
            COMPILE_TIME_CHECK_V2((!WEX::Common::AreSameType<TPrototype, const wchar_t>::value),
                    "You must explicitly specify a function prototype (via TPrototype) when issuing function queries.", 
                    You_must_explicitly_specify_TPrototype_when_issuing_function_queries);

            return ExternalMockFunction<TPrototype>(target, pTargetFunction, pDetourFunction, pszFilter);
        }
        
        template <typename TPrototype>
        static ExternalMockFunction<TPrototype> ExternalFunction(const ExternalMockTarget& target, const wchar_t* pszTargetBinary, const wchar_t* pszTargetQuery, 
                                                                 unsigned long expectedMatchCount = 1)
        {
            return ExternalMockFunction<TPrototype>(target, pszTargetBinary, pszTargetQuery, expectedMatchCount);
        }

        template <typename TPrototype>
        static ExternalMockFunction<TPrototype> ExternalFunction(const ExternalMockTarget& target, const wchar_t* pszTargetBinary, const wchar_t* pszTargetQuery, 
                                                                 const wchar_t* pszFilter, unsigned long expectedMatchCount = 1)
        {
            return ExternalMockFunction<TPrototype>(target, pszTargetBinary, pszTargetQuery, pszFilter, expectedMatchCount);
        }

        template <typename TPrototype>
        static ExternalMockFunction<TPrototype> ExternalFunction(const ExternalMockTarget& target, const wchar_t* pszTargetBinary, const wchar_t* pszTargetQuery, 
                                                                 typename ExternalMockFunction<TPrototype>::TDetourPrototype* pDetourFunction, unsigned long expectedMatchCount = 1)
        {
            return ExternalMockFunction<TPrototype>(target, pszTargetBinary, pszTargetQuery, pDetourFunction, expectedMatchCount);
        }

        template <typename TPrototype>
        static ExternalMockFunction<TPrototype> ExternalFunction(const ExternalMockTarget& target, const wchar_t* pszTargetBinary, const wchar_t* pszTargetQuery, 
                                                                 typename ExternalMockFunction<TPrototype>::TDetourPrototype* pDetourFunction, 
                                                                 const wchar_t* pszFilter, unsigned long expectedMatchCount = 1)
        {
            return ExternalMockFunction<TPrototype>(target, pszTargetBinary, pszTargetQuery, pDetourFunction, pszFilter, expectedMatchCount);
        }

        ////////////////////////////////
        // Class instance method mocking
        template <typename TPrototype>
        static MockMethod<typename WEX::Common::ParameterTypes<TPrototype>::PrototypeWithoutThrowSpecifier> Method(TPrototype targetFunction)
        { 
            return MockMethod<typename WEX::Common::ParameterTypes<TPrototype>::PrototypeWithoutThrowSpecifier>(targetFunction);
        }

        template <typename TPrototype>
        static MockMethod<typename WEX::Common::ParameterTypes<TPrototype>::PrototypeWithoutThrowSpecifier> Method(TPrototype targetFunction, const wchar_t* pszFilter)
        {
            Mock10::Private::WriteCriticalSectionLock lock(Mock10::Private::Detour::GetGlobalCriticalSection());
            MockMethod<typename WEX::Common::ParameterTypes<TPrototype>::PrototypeWithoutThrowSpecifier> mock(targetFunction);
            mock.SetFilter(pszFilter);
            return mock;
        }

        template <typename TPrototype, typename TPrototype2>
        static MockMethod<typename WEX::Common::ParameterTypes<TPrototype>::PrototypeWithoutThrowSpecifier> Method(TPrototype targetFunction, TPrototype2* pDetourFunction)
        {
            COMPILE_TIME_CHECK_V2((WEX::Common::AreSameType<Private::DetourMethod<TPrototype>::Prototype, TPrototype2>::value),
                    "The detour function specified does not have the expected function signature for an instance method mock. Please recheck the signature of the detour specified",
                    The_detour_function_specified_does_not_have_the_expected_function_signature_for_an_instance_method_mock_Please_recheck_the_signature_of_the_detour_specified);
            return MockMethod<typename WEX::Common::ParameterTypes<TPrototype>::PrototypeWithoutThrowSpecifier>(targetFunction, pDetourFunction);
        }

        template <typename TPrototype, typename TPrototype2>
        static MockMethod<typename WEX::Common::ParameterTypes<TPrototype>::PrototypeWithoutThrowSpecifier> Method(TPrototype targetFunction, TPrototype2* pDetourFunction, const wchar_t* pszFilter)
        {
            Mock10::Private::WriteCriticalSectionLock lock(Mock10::Private::Detour::GetGlobalCriticalSection());
            MockMethod<typename WEX::Common::ParameterTypes<TPrototype>::PrototypeWithoutThrowSpecifier> mock = Method(targetFunction, pDetourFunction);
            mock.SetFilter(pszFilter);
            return mock;
        }

        template <typename TPrototype, typename TFunctor>
        static MockMethod<typename WEX::Common::ParameterTypes<TPrototype>::PrototypeWithoutThrowSpecifier> Method(TPrototype targetFunction, const TFunctor& detourFunctor)
        {
            return MockMethod<typename WEX::Common::ParameterTypes<TPrototype>::PrototypeWithoutThrowSpecifier>(targetFunction, detourFunctor);
        }

        template <typename TPrototype, typename TFunctor>
        static MockMethod<typename WEX::Common::ParameterTypes<TPrototype>::PrototypeWithoutThrowSpecifier> Method(TPrototype targetFunction, const TFunctor& detourFunctor, const wchar_t* pszFilter)
        {
            Mock10::Private::WriteCriticalSectionLock lock(Mock10::Private::Detour::GetGlobalCriticalSection());
            MockMethod<typename WEX::Common::ParameterTypes<TPrototype>::PrototypeWithoutThrowSpecifier> mock = Method(targetFunction, detourFunctor);
            mock.SetFilter(pszFilter);
            return mock;
        }

        template <typename TPrototype>
        static MockMethod<typename WEX::Common::ParameterTypes<TPrototype>::PrototypeWithoutThrowSpecifier> Method(const wchar_t* pszTargetBinary, const wchar_t* pszTargetQuery, unsigned long expectedMatchCount = 1)
        {
            return MockMethod<typename WEX::Common::ParameterTypes<TPrototype>::PrototypeWithoutThrowSpecifier>(pszTargetBinary, pszTargetQuery, expectedMatchCount);
        }

        template <typename TPrototype>
        static MockMethod<typename WEX::Common::ParameterTypes<TPrototype>::PrototypeWithoutThrowSpecifier> Method(const wchar_t* pszTargetBinary, const wchar_t* pszTargetQuery, const wchar_t* pszFilter, unsigned long expectedMatchCount = 1)
        {
            Mock10::Private::WriteCriticalSectionLock lock(Mock10::Private::Detour::GetGlobalCriticalSection());
            MockMethod<typename WEX::Common::ParameterTypes<TPrototype>::PrototypeWithoutThrowSpecifier> mock = Method<TPrototype>(pszTargetBinary, pszTargetQuery, expectedMatchCount);
            mock.SetFilter(pszFilter);
            return mock;
        }

        /////////////////////////////////////
        // External class instance method mocking
        template <typename TPrototype>
        static ExternalMockMethod<typename WEX::Common::ParameterTypes<TPrototype>::PrototypeWithoutThrowSpecifier> ExternalMethod(const ExternalMockTarget& target, TPrototype targetFunction)
        {
            return ExternalMockMethod<typename WEX::Common::ParameterTypes<TPrototype>::PrototypeWithoutThrowSpecifier>(target, targetFunction);
        }

        template <typename TPrototype>
        static ExternalMockMethod<typename WEX::Common::ParameterTypes<TPrototype>::PrototypeWithoutThrowSpecifier> ExternalMethod(const ExternalMockTarget& target, TPrototype targetFunction, const wchar_t* pszFilter)
        {
            return ExternalMockMethod<typename WEX::Common::ParameterTypes<TPrototype>::PrototypeWithoutThrowSpecifier>(target, targetFunction, pszFilter);
        }

        template <typename TPrototype>
        static ExternalMockMethod<typename WEX::Common::ParameterTypes<TPrototype>::PrototypeWithoutThrowSpecifier> ExternalMethod(const ExternalMockTarget& target, TPrototype targetFunction, 
                                                                                                                                   typename ExternalMockMethod<typename WEX::Common::ParameterTypes<TPrototype>::PrototypeWithoutThrowSpecifier>::TDetourPrototype* pDetourFunction)
        {
            // Make sure to specify TPrototype in your call to Mock::ExternalFunction<TPrototype(pszTargetBinary, pszQuery, ...)
            COMPILE_TIME_CHECK_V2((!WEX::Common::AreSameType<TPrototype, const wchar_t>::value),
                    "You must explicitly specify a function prototype (via TPrototype) when issuing function queries.", 
                    You_must_explicitly_specify_TPrototype_when_issuing_function_queries);

            return ExternalMockMethod<typename WEX::Common::ParameterTypes<TPrototype>::PrototypeWithoutThrowSpecifier>(target, targetFunction, pDetourFunction);
        }

        template <typename TPrototype>
        static ExternalMockMethod<typename WEX::Common::ParameterTypes<TPrototype>::PrototypeWithoutThrowSpecifier> ExternalMethod(const ExternalMockTarget& target, TPrototype targetFunction, 
                                                                                                                                   typename ExternalMockMethod<typename WEX::Common::ParameterTypes<TPrototype>::PrototypeWithoutThrowSpecifier>::TDetourPrototype* pDetourFunction, 
                                                                                                                                   const wchar_t* pszFilter)
        {
            // Make sure to specify TPrototype in your call to Mock::ExternalFunction<TPrototype(pszTargetBinary, pszQuery, ...)
            COMPILE_TIME_CHECK_V2((!WEX::Common::AreSameType<TPrototype, const wchar_t>::value),
                    "You must explicitly specify a function prototype (via TPrototype) when issuing function queries.", 
                    You_must_explicitly_specify_TPrototype_when_issuing_function_queries);

            return ExternalMockMethod<typename WEX::Common::ParameterTypes<TPrototype>::PrototypeWithoutThrowSpecifier>(target, targetFunction, pDetourFunction, pszFilter);
        }

        template <typename TPrototype>
        static ExternalMockMethod<typename WEX::Common::ParameterTypes<TPrototype>::PrototypeWithoutThrowSpecifier> ExternalMethod(const ExternalMockTarget& target, const wchar_t* pszTargetBinary, const wchar_t* pszTargetQuery, unsigned long expectedMatchCount = 1)
        {
            return ExternalMockMethod<typename WEX::Common::ParameterTypes<TPrototype>::PrototypeWithoutThrowSpecifier>(target, pszTargetBinary, pszTargetQuery, expectedMatchCount);
        }

        template <typename TPrototype>
        static ExternalMockMethod<typename WEX::Common::ParameterTypes<TPrototype>::PrototypeWithoutThrowSpecifier> ExternalMethod(const ExternalMockTarget& target, const wchar_t* pszTargetBinary, const wchar_t* pszTargetQuery, 
                                                                                                                                   const wchar_t* pszFilter, unsigned long expectedMatchCount = 1)
        {
            return ExternalMockMethod<typename WEX::Common::ParameterTypes<TPrototype>::PrototypeWithoutThrowSpecifier>(target, pszTargetBinary, pszTargetQuery, pszFilter, expectedMatchCount);
        }

        template <typename TPrototype>
        static ExternalMockMethod<typename WEX::Common::ParameterTypes<TPrototype>::PrototypeWithoutThrowSpecifier> ExternalMethod(const ExternalMockTarget& target, const wchar_t* pszTargetBinary, const wchar_t* pszTargetQuery, 
                                                                                                                                   typename ExternalMockMethod<typename WEX::Common::ParameterTypes<TPrototype>::PrototypeWithoutThrowSpecifier>::TDetourPrototype* pDetourFunction, 
                                                                                                                                   unsigned long expectedMatchCount = 1)
        {
            return ExternalMockMethod<typename WEX::Common::ParameterTypes<TPrototype>::PrototypeWithoutThrowSpecifier>(target, pszTargetBinary, pszTargetQuery, pDetourFunction, expectedMatchCount);
        }

        template <typename TPrototype>
        static ExternalMockMethod<typename WEX::Common::ParameterTypes<TPrototype>::PrototypeWithoutThrowSpecifier> ExternalMethod(const ExternalMockTarget& target, const wchar_t* pszTargetBinary, const wchar_t* pszTargetQuery, 
                                                                                                                                   typename ExternalMockMethod<typename WEX::Common::ParameterTypes<TPrototype>::PrototypeWithoutThrowSpecifier>::TDetourPrototype* pDetourFunction, 
                                                                                                                                   const wchar_t* pszFilter, unsigned long expectedMatchCount = 1)
        {
            return ExternalMockMethod<typename WEX::Common::ParameterTypes<TPrototype>::PrototypeWithoutThrowSpecifier>(target, pszTargetBinary, pszTargetQuery, pDetourFunction, pszFilter, expectedMatchCount);
        }

        // Do not intentionally call this version of the API; it is here only to provide a helpful compiler error in invalid use cases
        template <typename TInvalid>
        static ExternalMockMethod<TInvalid> ExternalMethod(const ExternalMockTarget&, const wchar_t*, TInvalid* pInvalid)
        {
            return ExternalMockMethod<TInvalid>(pInvalid);
        }

        /////////////////////////////////////
        // Global variable modification
        template <typename TVariableType>
        static MockVariable<TVariableType> Variable(const wchar_t* pszTargetBinary, const wchar_t* pszTargetQuery)
        {
            return MockVariable<TVariableType>(pszTargetBinary, pszTargetQuery);
        }

    private:
        Mock(); // Not implemented
        ~Mock(); // Not implemented
    };
} /* namespace Mock10 */

#pragma pop_macro("SEALED")

